package contegor.scire.structure.queries;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import contegor.scire.core.Concept;
import contegor.scire.core.Container;
import contegor.scire.core.Id;
import contegor.scire.core.InfiniteSingletonList;
import contegor.scire.core.Link;
import contegor.scire.core.NeedsPrecedent;
import contegor.scire.core.ReturnPoint;
import contegor.scire.core.ReturnPointAdapter;
import contegor.scire.core.Task;
import contegor.scire.core.TaskId;
import contegor.scire.core.Version;
import contegor.scire.core.WithConsequences;
import contegor.scire.core.queries.Condition;
import contegor.scire.core.queries.GetConcepts;
import contegor.scire.core.queries.Query;

/**
 * {@link CircularQuery} that, with a {@link Concept}s set given by its {@link NeedsPrecedent} interface,
 * gets the target {@link Concept}s from original {@link Concept}'s links that comply with a given condition.
 *
 * @version $Revision$
 * @author  Héctor Cirbián Pousa ($Author$)
 */
@Version("$Id$")
public class CircularGetConceptsUpstream extends CircularQuery<Concept> implements WithConsequences<Concept, Concept> {
  private static final long serialVersionUID = -8609002380600485841L;

  private List<? extends Concept> precedentResults = null;
  private List<Query<Concept>>    consequences     = null;
  private List<Concept>           results          = Collections.synchronizedList(new ArrayList<Concept>());
  private Condition<Link>         condition        = null;

  private transient ReturnPoint<Concept> consequencesReturnPoint = null;

  public void setCondition(Condition<Link> condition) {
    this.condition = condition;
  }

  @Override
  public void setPrecedent(Task<? extends Concept> task, Collection<? extends Concept> results) {
    this.precedentResults = new ArrayList<Concept>(results);
  }

  @Override
  public void process(Container targetContainer) {
    if (this.precedentResults != null) {
      this.consequences = new ArrayList<Query<Concept>>();
      for (Concept concept: this.precedentResults) {
        if (concept.isVisibleTo(this.taskId.getTransactionId())) {
          for (Link link:concept.getLinks().values()) {
            if (condition.eval(link)) {
              Id id = link.getTarget();
              GetConcepts consequence = new GetConcepts(id.getParentId(), id.getLastIdFragment());
              consequence.setTaskId(this.taskId.next(true));
              this.consequences.add(consequence);
            }
          }
        }
      }
    }
  }

  @Override
  public List<? extends Task<Concept>> getConsequences() {
    return this.consequences;
  }

  @Override
  public List<? extends ReturnPoint<? super Concept>> getConsequencesReturnPoints() {
    if ((this.consequences == null) || (this.consequences.size() == 0))
      return Collections.emptyList();
    if (this.consequencesReturnPoint == null)
      this.consequencesReturnPoint = generateConsequencesReturnPoint();
    return new InfiniteSingletonList<ReturnPoint<? super Concept>>(this.consequencesReturnPoint, consequences.size());
  }

  @Override
  public List<? extends Collection<Concept>> getResultsForSubsequentTasks() {
    if ((this.subsequents == null) || (this.subsequents.size() == 0))
      return Collections.emptyList();
    return new InfiniteSingletonList<List<Concept>>(this.results, this.subsequents.size());
  }

  @Override
  public List<? extends ReturnPoint<? super Concept>> getSubsequentTasksReturnPoints() {
    if ((this.subsequents == null) || (this.subsequents.size() == 0))
      return Collections.emptyList();
    return new InfiniteSingletonList<ReturnPoint<? super Concept>>(this.returnPoint, subsequents.size());
  }

  @Override
  public CircularGetConceptsUpstream withIt(NeedsPrecedent<? super Concept, Concept> subsequent) {
    super.withIt(subsequent);
    return this;
  }

  private ReturnPoint<Concept> generateConsequencesReturnPoint() {
    return new ReturnPointAdapter<Concept>() {
      public void manage(TaskId taskId, Concept result) {
        results.add(result);
      }
      public void manage(TaskId taskId, Exception exception) {
        returnPoint.manage(taskId, exception);
      }
    };
  }
}
